﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
//文件操作

/* C语言在程序启动时默认打开三个流
• stdin-标准输⼊流，在⼤多数的环境中从键盘输⼊，scanf函数就是从标准输⼊流中读取数据。
• stdout-标准输出流，⼤多数的环境中输出⾄显⽰器界⾯，printf函数就是将信息输出到标准输出流中。
• stderr-标准错误流，⼤多数环境中输出到显⽰器界⾯。*/

//打开文件：fopen()
//原型：FILE *open(const char* filename,const char* mode);
/*功能：fopen原来打开文件名为filename的文件，同时将打开的文件与一个流进行关联，并返回一个指向流的指针，
后面就通过这个指针来维护流（关联文件），具体的操作根据mode来指定*/
/*参数：
1. filename即文件名：文件路径（绝对路径或相对路径）+文件名主干+文件后缀
2.mode:对文件的操作方式，例如，"r"--只读；"w"--只写；"a"--追加*/

//关闭文件：fclose()
//原型：int fclose(FILE*stream);
/*关闭参数stream 关联的⽂件，并取消其关联关系。与该流关联的所有内部缓冲区均会解除关联并刷新：任何未写⼊的输出缓冲区内容将被写⼊，任何未读取的输⼊缓冲区内容将被丢弃。*/

int main()
{
  //相对路径写法，表示date.txt在当前工程目录下
	FILE* pf = fopen("date.txt", "r");//"r"只读，当指定文件不存在时，出错
  //绝对路径写法
  //FILE* pf = fopen("D:/storage_c/hds_-c/date.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	printf("打开文件成功，可以操作\n");
	fclose(pf);//关闭文件
	pf = NULL;//规避野指针
}

int main()
{
	FILE* pf = fopen("date.txt", "w");//"r"只写，当指定文件不存在时，新建一个文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	printf("打开文件成功，可以操作\n");
	fclose(pf);
	pf = NULL;
}

//向⽂件或输出流中写⼊一个字符：fputc()
//原型：int fputc(int character,FILE* stream)
/*将参数character 指定的字符写⼊到stream 指向的输出流中，通常⽤于向⽂件或标准输出流写⼊字符。
在写⼊字符之后，还会调整指⽰器。字符会被写⼊流内部位置指⽰器当前指向的位置，随后该指⽰器⾃动向前移动⼀个位置。*/
//当成功写入后返回写入的字符（以int类型返回），失败则返回EOF(-1),错误指示器会被设置，可以通过ferror()检查具体错误
int main()
{
	FILE* pf = fopen("date.txt", "w");//"w",表示只写，只有以w的形式打开文件，fputc()才能正确的写文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = 0;
	ch = fputc('a', pf);
	printf("%c\n", ch);
	putchar(ch);
	fputc(ch, stdout);//stdout(标准输出流)，第61，62，63行代码作用相同
	fputc('b', pf);
	fputc('c', pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

//循环写入
int main()
{
	FILE* pf = fopen("date.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = 0;
	for (ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, pf);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}


//从⽂件或输出流中读取一个字符：fgetc()
//原型:int fgetc(FILE* stream);
/*从参数stream 指向的流中读取⼀个字符。函数返回的是⽂件指⽰器当前指向的字符，读取这个字符之后，⽂件指⽰器⾃动前进到下⼀个字符。*/
//参数：stream:可以是FILE* 类型的⽂件指针，可以是stdin，也可以是其他输⼊流的指针。如果是stdin就从标准输⼊流读取数据。如果是⽂件流指针，就从⽂件读取数据。
/*返回值：
• 成功时返回读取的字符（以int 形式）。
• 若调⽤时流已处于⽂件末尾，函数返回(EOF),并设置流的⽂件结束指⽰器(feof)。若发⽣读取错误，返回(EOF)并设置流的错误指⽰器（ferror ）。*/

int main()
{
	FILE* pf = fopen("date.txt", "r");//"r",表示只读，只有以r的形式打开文件，fgetc()才能正确的读文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = 0;
	ch = fgetc(pf);
	fputc(ch, stdout);
	fclose(pf);
	pf = NULL;
	return 0;
}


//feof(): 检测stream指针指向的流是否遇到⽂件末尾
//原型：int feof ( FILE * stream );
/*如果在读取⽂件的过程中，遇到了⽂件末尾，⽂件读取就会结束。这时读取函数会在对应的流上设置⼀个⽂件结束的指⽰符，
这个⽂件结束指⽰符可以通过feof 函数检测到。如果feof 函数检测到⽂件结束指⽰符已经被设置，则返回⾮0的值，如果没有设置则返回0。*/

//ferror(): //检测stream指针指向的流是否发⽣读写错误
//原型：int ferror ( FILE * stream);
/*如果在读/写⽂件的过程中，发⽣了读/写错误，⽂件读取就会结束。这时读/写函数会在对应的流上设置⼀个错误指⽰符，
这个错误指⽰符可以通过ferror 函数检测到。如果ferror函数检测到错误指示符已经被设置，则返回非0的值，未设置则返回0 */

int main()
{
	FILE* pf = fopen("date.txt", "r");//"r",表示只读，只有以r的形式打开文件，fgetc()才能正确的读文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	for(int i=1;i<5;i++)
	{
		int ch = fgetc(pf);
		if (ch == EOF)
		{
			if (feof(pf))
				printf("遇到文件末尾了\n");
			else if (ferror(pf))
				printf("发生了读写错误\n");
		}
		else
			fputc(ch, stdout);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

int main()
{
	FILE* pf = fopen("date.txt", "w");//将上面的"r"改为"w",以写的形式打开文件，fgetc()函数无法正确的读文件，发生读写错误，来用ferror函数检测
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	for (int i = 1; i < 5; i++)
	{
		int ch = fgetc(pf);
		if (ch == EOF)
		{
			if (feof(pf))
				printf("遇到文件末尾了\n");
			else if (ferror(pf))
				printf("发生了读写错误\n");
		}
		else
			fputc(ch, stdout);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

//从键盘读取数据
int main()
{
	int ch = fgetc(stdin);
	fputc(ch, stdout);
	return 0;
}

//fputs():用于在文件流或输出流中写入一串字符
//原型：int fputs ( const char * str, FILE * stream );
/*将参数str 指向的字符串(必须以'\0'结尾)写⼊到参数stream指定的流中（不包含结尾的空字符\0 ），适⽤于⽂件流或标准输出（stdout）。*/
//成功则返回非负整数;失败则返回EOF(-1),并设置流的错误指示器，可以用ferror检查错误原因

int main()
{
	FILE* pf = fopen("date.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("abcd", pf);
	int ret = fputs("hello world",pf);
	if (ret == EOF)
	{
		if (ferror(pf))
			perror("fputs");
	}
	else
		printf("%d\n", ret);
	fclose(pf);
	pf = NULL;
	return 0;
}

//读写错误展示
int main()
{
	FILE* pf = fopen("date.txt", "r");//将上面的'w'(写)改为'r'(读)，fputs会发生读写错误
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("abcd", pf);
	int ret = fputs("hello world", pf);
	if (ret == EOF)
	{
		if (ferror(pf))
			perror("fputs");//fputs: Bad file descriptor
	}
	else
		printf("%d\n", ret);
	fclose(pf);
	pf = NULL;
	return 0;
}


//fgets():用于从文件流或输入流中读取一串字符
// 原型：char * fgets ( char * str, int num, FILE * stream );
//从stream指定输⼊流中读取字符串，⾄读取到换⾏符、⽂件末尾（EOF）或达到指定字符数（包含结尾的空字符\0），然后将读取到的字符串存储到str指向的空间中。
/*参数：
• str：是指向字符数组的指针，str指向的空间⽤于存储读取到的字符串。
• num：最⼤读取字符数（包含结尾的\0，实际最多读取num-1 个字符）。
• stream ：输⼊流的⽂件指针（如⽂件流或stdin ）。*/
/*返回值：
• 成功时返回str 指针。
• 若在尝试读取字符时遇到⽂件末尾，则设置⽂件结束指⽰器，返回NULL，需要用feof检查错误原因
•若发⽣读取错误，则设置流错误指⽰器，并返回NULL，通过ferror检查错误原因*/

int main()
{
	FILE* pf = fopen("date.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	char arr[20]="xxxxxxxxxxxxxx";
	char* c = fgets(arr, 10, pf);
	if (c == NULL)
	{
		if (feof(pf))
			printf("遇到文件末尾\n");
		else if (ferror(pf))
			printf("发生读写错误\n");
	}
	else
		printf("%p\n", c);
	fclose(pf);
	pf = NULL;
	return 0;
}

//从键盘读取数据
int main()
{
	char arr[10];
	fgets(arr,5,stdin);//从键盘输入的字符为5-1个，包含'\0'
	fputs(arr, stdout);
	return 0;
}


//fprintf()
//原型：int fprintf ( FILE * stream, const char * format, ... );
/*fprintf 是将格式化数据写⼊指定⽂件流的函数。它与printf 类似，但可以输出到任意⽂件（如磁盘⽂件、标准输出、标准错误等），⽽不仅限于控制台。*/
/*参数：
• stream ：指向FILE 对象的指针，表⽰要写⼊的⽂件流（如文件指针，stdout)
• format ：格式化字符串，包含要写⼊的⽂本和格式说明符（如%d 、%s等）。
• ...：可变参数列表，提供与格式字符串中说明符对应的数据。*/
/*返回值：
• 成功时，返回写⼊的字符总数（⾮负值）。
• 失败时，先设置对应流的错误指⽰器，再返回负值，可以通过ferror() 来检测。*/
struct stu
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct stu s = { "zhangsang",20,85.5 };
	FILE* pf = fopen("data.txt", "w");//"w",以写的形式打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ret = fprintf(pf, "%s %d %.1f\n", s.name, s.age, s.score);
	if (ret < 0)
	{
		if (ferror(pf))
			perror("fprintf");
	}
	else
		printf("%d\n", ret);//17 = 所有字符个数+3个'\0'
	fclose(pf);
	pf = NULL;
	return 0;
}

//fscanf()
//原型：int fscanf(FILE* stream, const char* format, ...);
/*：fscanf 是从指定⽂件流中读取格式化数据的函数。它类似于scanf ，但可以指定输⼊源（如⽂件、标准输⼊等），⽽⾮仅限于控制台输⼊。
适⽤于从⽂件解析结构化数据（如整数、浮点数、字符串等）。*/
/*参数：
• stream ：指向FILE 对象的指针，表⽰要读取的⽂件流（如stdin 、⽂件指针等）。
• format ：格式化字符串，定义如何解析输⼊数据（如%d 、%f、%s等）。
• ...：可变参数列表，提供存储数据的变量地址（需与格式字符串中的说明符匹配）。*/
/*返回值：
• 成功时，函数返回成功填充到参数列表中的项数。(可能小于预期值)
•如果在成功读取任何数据之前发⽣：
◦发⽣读取错误，会在对应流上设置错误指⽰符，则返回EOF（可以用ferror检测错误）
◦到达⽂件末尾，会在对应流上设置⽂件结束指⽰符，则返回EOF(可以用feof检测)*/

struct stu
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct stu s = { "zhangsan",20,85.5 };
	FILE* pf = fopen("data.txt", "w");//"w",以写的形式打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//假设在上面fprintf()学习时，fprintf()已经在文件中写入了数据zhangsan 20 85.5，
	// 现在用fscanf()读取文件中的这些数据，并打印
	fscanf(pf, "%s %d %.1f", s.name, &(s.age), &(s.score));
	fprintf(stdout, "%s %d %.1f\n", s.name, s.age, s.score);
	fclose(pf);
	pf = NULL;
	return 0;
}